package mole.entities 
{
	import flash.system.IMEConversionMode;
	import mole.CSoundAssets;
	import mole.CSoundManager;
	import mole.entities.obstacles.CObstacle;
	import mole.entities.obstacles.CObstaclesAssets;
	import mole.hud.CStamina;
	import mole.Main;
	import flash.geom.Point;
	import flash.utils.ByteArray;
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Anim;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.graphics.Spritemap;
	import net.flashpunk.masks.Grid;
	import mole.entities.controlers.IControler;
	import net.flashpunk.Sfx;
	
	
	/**
	 * Représente une entité du jeu de type joueur/adversaire.
	 * @author Cédric Liaudet
	 */
	public class CPlayer extends Entity
	{		
		[Embed(source = '../../data/sounds/Mole_Loop_Plonge.mp3')]
		static public const PLONGE_SFX:Class;
		
		/**
		 * Constructeur par défaut.
		 * @param	_PosX Position sur l'axe des X.
		 * @param	_PosY Position sur l'axe des Y.
		 * @param   _Controler Le controleur du personnage.
		 * @param   _iType Type de sprite.
		 */
		public function CPlayer(_PosX:Number, _PosY:Number, _Controler:IControler, _iType:Number, _iColor:int, _iNb:int) 
		{
			m_ExtControler = null;
			
			// On position le joueur.
			x = _PosX;
			y = _PosY;
			
			// Initialisation des variables.
			m_iLife = 0;
			m_iModjo = 0;
			m_Controler = _Controler;
			m_bJump = true;			
			m_bFall = true;
			m_fJumpForce = 0.0;
			m_fJumpDuration = 3.0;
			m_bCanMove = true;
			m_iVelocity = 0;
			m_accRate = 2;
			m_safeDuration = 0;
			m_bGoSwim = false;
			m_bSwim = false;
			m_Stamina = new CStamina(_iNb, (_iNb * 256) + 24);
			m_nStamina = 100;
			m_nId = _iNb;
			
			m_PlongeSfx = new Sfx(PLONGE_SFX);
							
			// Chargement du personnage.
			Load(CPlayerAssets.s_Charaters[_iType], _iColor);
		}
		
		public function Init():void
		{
			m_Stamina.Init();
		}
		
		public function GetId():int
		{
			return m_nId;
		}
		
		/**
		 * Chargement du perso.
		 * @param	_Character
		 */
		protected function Load(_Character:Class, _iColor:int):XML
		{
			// Lecture du fichier XML.
			var file:ByteArray = new _Character;
			var str:String = file.readUTFBytes( file.length );
			var xml:XML = new XML( str );
			
			// On recupere les attributs.
			if (xml.attributes)
			{
				// <commons life="50" modjo="0" velocity="5" />
				m_iLife = xml.attributes.@life;
				m_iSpeed = xml.attributes.@velocity; // pas utlisé pour le moment.
				
				if(xml.attributes.hasOwnProperty("@modjo"))
					m_iModjo = xml.attributes.@modjo;
				
				if(xml.attributes.hasOwnProperty("@jump_duration"))
					m_fJumpDuration = xml.attributes.@jump_duration;
			}
			
			// Creation du sprite
			if (xml.sprite)
			{
				var sprite:int = xml.sprite.@id;
				sprite += _iColor;
				
				// creation des animations
				if (xml.sprite.animations)
				{
					for each (var animations:XML in xml.sprite.animations)
					{
						// <animations width="48" height="32">
						width = animations.@width;
						height = animations.@height;
						var spriteAnim:Spritemap = new Spritemap(CPlayerAssets.s_Sprites[sprite], animations.@width, animations.@height, OnAnimationEnd);					
						
						// on parcours toute les animations.
						for each(var anim:XML in animations.anim)
						{
							// <anim name="idle" tile="0, 1, 2, 3, 4, 5" speed="20" loop="true" />
							spriteAnim.add(anim.@name, (anim.@tile).split(", "), anim.@speed, true);
							m_aAnimations[anim.@name] = spriteAnim;
						}											
					}
					
					// on applique l'animation par défaut.
					PlayAnim("idle");
				}
				else
					graphic = new Image(CPlayerAssets.s_Sprites[sprite]);				
			}
			
			return xml;
		}
		
		/**
		 * Callback de mise à jours.
		 */
		override public function update():void
		{
			if (!m_iVelocity)
				return;
				
			// On applique la gravite si le personnage tombe.
			if (m_bFall && !m_bSwim)
			{
				y += (Main.World.gravity - m_fJumpForce) * net.flashpunk.FP.elapsed;
					
				// Mise à jours de la force du saut.
				if (m_bJump && m_fJumpForce > 0)
				{					
					m_fJumpForce -= Main.World.gravity * net.flashpunk.FP.elapsed;
							
					if (m_fJumpForce < 0)
					{
						m_fJumpForce = 0.0
						m_bJump = false;
					}
				}
				// Acceleration de la chute.
				// 100 pixels ~= 1 metre
				else
					m_fJumpForce -= 100.0 * net.flashpunk.FP.elapsed;
			}

			// Détection des collisions avec la carte.
			if (m_bSwim)
			{				
				m_nStamina -= 20 * FP.elapsed;
				if (m_nStamina < 0)
				{
					m_nStamina = 0;
					OnDead();
					return;
				}
					
				if (m_bUp)
					y -= 40 * FP.elapsed;
				else if (m_bDown)
					y += 40 * FP.elapsed;
					
				if (y + height > 512)
					y = 512 - height;
				else if (y < 412)
				{
					m_bSwim = false;
					m_bGoSwim = false;
					y = 384 - height;
					OnLand();
				}
			}
			else
			{
				m_nStamina += 10 * FP.elapsed;
				if (m_nStamina > 100)
					m_nStamina = 100;
					
				if (m_bGoSwim)
				{
					if (y > 412)
					{
						y = 412;
						m_bSwim = true;
						m_bGoSwim = false;
						PlayAnim("swim", false);
						m_PlongeSfx.stop();
						Main.SoundManager.PlayRandomSound(CSoundAssets.s_RandomContainers[CSoundAssets.NAGE]);	
						
					}
				}
				else if (y + height > 384)
				{
					y = 384 - height;
					OnLand();
				}
			}			
			
			var obstacle:CObstacle = collide("obstacle", x, y) as CObstacle;
			if (obstacle)
			{
				var impact:Boolean = true;
				if (obstacle.GetId() == CObstaclesAssets.PLAFOND_2_PNG && m_bCrouch)
					impact = false;
				else if (obstacle.GetId() == CObstaclesAssets.SOL_1_PNG && m_bCrouch && obstacle.y <= y)
					impact = false;
				
				if(impact)
				{
					obstacle.active = false;
					x -= (obstacle.difficulty + 1) * 32;
					m_safeDuration = 0;											// On remet à 0 le temps passé sans collisionner
					obstacle.Explode();
					Main.SoundManager.PlayRandomSound(CSoundAssets.s_RandomContainers[CSoundAssets.IMPACT]);	
				}
			}
			
			/*var floor:CFloor = collide("solid", x, y) as CFloor;
			if (floor)
			{
				var grid:Grid = floor.grid;
				
				y = floor.y - height;
				OnLand();
				// on récupére la cellule courante (angle haut/gauche)
				/*var iXCell:uint = x / 32;
						
				// la cellule maximum (angle bas/droit).
				var iXCellMax:uint = (x + width) / 32;
						
				// collision verticale.
				/*if (m_iVelocity != 0)
				{										
					// trou a droite.
					//
					//  [ ] [ ] [ ]
					//  [ ] [P] [ ]
					//  [ ] [ ] [T]
					if (m_iVelocity > 0 && !grid.getCell(iXCellMax, 0))
					{
						OnHoleInFront(m_iVelocity);
					}
					// trou a gauche.
					//
					//  [ ] [ ] [ ]
					//  [ ] [P] [ ]
					//  [T] [ ] [ ]
					else if (m_iVelocity < 0 && !grid.getCell(iXCell, 0))
					{
						OnHoleInFront(m_iVelocity);
					}
				}
						
				// collision horizontale.
				if (m_bFall)
				{					
					for (var iX:uint = iXCell; iX <= iXCellMax; iX++)
					{
						if (iX * 32 >= x || (iX * 32) + 33 <= x)
						{
							// Collision du dessous
							//
							//  [ ] [ ] [ ]
							//  [ ] [P] [ ]
							//  [ ] [C] [ ]
							if (grid.getCell(iX, 0))
							{
								y = 310;
									
								OnLand();
							}
						}
					}
				}
				// Damned je tombe.
				//else
				//	OnFall();
			}*/
			
			// Déplacement du personnage.
			//if (m_bCanMove)
			//	x += m_iVelocity * FP.elapsed;
			
			if (m_safeDuration >= m_accRate)				// on est resté suffisamment longtemps sans collisionné pour avancer
			{
				//m_safeDuration = 0;
				if(m_bSwim)
					x += 50 * FP.elapsed;
				else
					x += 10 * FP.elapsed;
			}
			else if(y == 384 - height || m_bSwim)
			{
				m_safeDuration += FP.elapsed;
				if (m_safeDuration >= m_accRate)
				{					
					x += 10;
					Main.SoundManager.PlayRandomSound(CSoundAssets.s_RandomContainers[CSoundAssets.SPEED]);					
				}
			}
			
			if (x + width > 832)
				x = 832 - width;
							
			m_Stamina.SetStamina(m_nStamina);
				
			// Mise à jours du personnage.
			m_Controler.update(this);
			
			OnUpdate();
		}
		
		//==============================================//
		//					LES ACTIONS 				//
		//==============================================//
		/**
		 * Callback de MAJ du personnage.
		 */
		public function OnUpdate():void
		{
			
		}
				
		/**
		 * Callback lorsque le personnage meurt.
		 */
		public function OnDead():void
		{
			Main.World.remove(this);
		}
		
		/**
		 * Callback lorsque le personnage saute.
		 */
		public function OnJump():void
		{
			if (m_bGoSwim)
				return;
				
			if (m_bSwim)
			{
				m_bUp = true;
				//m_bDown = false;
			}
			else
			if (m_bCanMove && !m_bJump && !m_bSwim)
			{
				m_bFall = true;
				m_bJump = true;
				m_fJumpForce =  (Main.World.gravity * m_fJumpDuration) / 2.0;
				
				var bFlipped:Boolean = false;
				if (graphic)
					bFlipped = (graphic as Spritemap).flipped;
					
				PlayAnim("jump", bFlipped);
			}
		}
		
		public function ReleaseJump():void
		{
			if (m_bGoSwim)
				return;
				
			if (m_bSwim)
				m_bUp = false;
			else if (!m_bGoSwim)
			{
				m_bFall = true;
				m_fJumpForce = 70.0;
			}
		}
		
		/**
		 * Callback lorsque le personnage tombe.
		 */
		public function OnFall():void
		{
			if (!m_bFall)
			{
				m_bFall = true;
				
				if (m_fJumpForce > 10.0)
					m_fJumpForce = 10.0;
					
				var bFlipped:Boolean = false;
				if (graphic)
					bFlipped = (graphic as Spritemap).flipped;
						
				PlayAnim("jump", bFlipped);
			}
		}
		
		/**
		 * Callback lorsque le personnage atterri
		 */
		public function OnLand():void
		{
			m_bFall = m_bJump = false;
			
			var bFlipped:Boolean = false;
			if (graphic)
				bFlipped = (graphic as Spritemap).flipped;
				
			PlayAnim("land", bFlipped);
		}
		
		/**
		 * Callback lorsque le personnage va a gauche.
		 */
		public function OnLeft():void
		{
			m_iVelocity = -GetCurrentSpeed();
				
			OnChangeDirection(true);
		}
		
		/**
		 * Callback lorsque le personnage va a droite.
		 */
		public function OnRight():void
		{
			m_iVelocity = GetCurrentSpeed();
				
			OnChangeDirection(false);
		}
		
		/**
		 * Callback lorsque le personnage s'arrête.
		 */
		public function OnStop():void
		{
			PlayAnim("idle", m_iVelocity > 0);
			m_iVelocity = 0;			
		}
		
		/**
		 * Application l'animation selon la direction.
		 * @param	_bFlipped Orientation de l'animation.
		 */
		private function OnChangeDirection(_bFlipped:Boolean):void
		{
			if (m_bGoSwim)
				return;
				
			if (m_bCanMove)
			{
				if (!m_bFall)
				{
					if (m_iVelocity)
					{
						if (m_iVelocity < 0)
							_bFlipped = true;
						else
							_bFlipped = false;
						
						PlayAnim("run", _bFlipped);
					}
					else
						PlayAnim("idle", _bFlipped);
				}
				else if(graphic)
					(graphic as Spritemap).flipped = _bFlipped;
			}
		}
		
		/**
		 * Callback lorsque le personnage s'accroupie.
		 */
		public function OnDown():void
		{		
			if (m_bGoSwim)
				return;
				
			if (m_bSwim)
			{
				//m_bUp = false;
				m_bDown = true;
			}
			else 
			{
				if (m_bJump)
				{
					// plonge!
					m_bGoSwim = true;
					m_bUp = false;
					m_bDown = false;
					PlayAnim("creuse", false);
					m_PlongeSfx.play();
					Main.SoundManager.PlayRandomSound(CSoundAssets.s_RandomContainers[CSoundAssets.PLONGE]);	
				}
				else
				{					
					m_bCrouch = true;
					PlayAnim("crouch", false);
				}
			}
		}
		
		/**
		 * Callback lorsque le personnage se relève
		 */
		public function OnUp():void
		{
			if (m_bGoSwim)
				return;
				
			if(m_bSwim)
			{
				//m_bUp = false;
				m_bDown = false;
			}
			else
			{
				m_bCrouch = false;
				PlayAnim("run", false);
			}
		}
		
		/**
		 * Callback lorsque le personnage attaque.
		 */
		public function OnAttack():void
		{
			if (m_bCanMove && !m_bFall)
			{
				m_bCanMove = false;				
				
				var bFlipped:Boolean = false;
				var spriteMap:Spritemap = graphic as Spritemap;
				if (spriteMap)
					bFlipped = spriteMap.flipped;
					
				PlayAnim("attack", bFlipped);
			}
		}
		
		/**
		 * Callback lorsque le personnage recoit des dégats.
		 * @param	_iDamage Point de dommage.
		 */
		public function OnHit(_iDamage:int):void
		{
			m_iLife -= _iDamage;
		}
		
		/**
		 * Callback lorsque qu'il y a un trou en face du personnage.
		 * @param _iVelocity Vitesse de deplacement du personnage (V < 0 lorsqu'il se déplace a gauche, V > 0 lorsqu'il se déplace a droite).
		 */
		public function OnHoleInFront(_iVelocity:int):void 
		{
			// ADIOS
		}
		
		/**
		 * On joue l'animation demandé.
		 * @param	_Anim le nom de l'animation.
		 */
		protected function PlayAnim(_sAnim:String, _bFlipped:Boolean = false):void
		{
			var spriteMap:Spritemap = m_aAnimations[_sAnim] as Spritemap;
			if (spriteMap)
			{
				// réajuste la position selon la hauteur.
				if (graphic)
				{
					var iLastHeight:Number = (graphic as Spritemap).height;
					y -= spriteMap.height - iLastHeight;
				}
				
				graphic = spriteMap;
				
				// on joue l'animation.
				spriteMap.flipped = _bFlipped;
				spriteMap.play(_sAnim, true);
				
				// on reajuste la boite de collision
				setHitbox(spriteMap.width, spriteMap.height);				
			}
		}
		
		/**
		 * 
		 */
		protected function OnAnimationEnd():void
		{
			var spriteMap:Spritemap = graphic as Spritemap;
			if (spriteMap)
			{
				// on joue l'animation idle ou run lorsque l'animation d'atterrissage est fini.
				if (spriteMap.currentAnim == "land" || spriteMap.currentAnim == "attack")
				{
					m_bCanMove = true;
					OnChangeDirection(spriteMap.flipped);					
				}
			}
		}
		
		/**
		 * On récupére le nombre de point d'amour actuel.
		 * @return Le nombre de point d'amour.
		 */
		public function get modjo():int { return m_iModjo; }
		
		/**
		 * On définie le nombre de point d'amour actuel.
		 * @param _iModjo Les nouveaux points.
		 */
		public function set modjo(_iModjo:int):void { m_iModjo = _iModjo; }
		
		/**
		 * Ajout des points d'amour.
		 * @param	_iModjo le nombre de point d'amour a ajouter.
		 */
		public function AddModjo(_iModjo:int):void
		{
			m_iModjo += _iModjo;
		}
		
		/**
		 * On supprime des points d'amour.
		 * @param	_iModjo le nombre de point d'amour a soustraire.
		 */
		public function SubModjo(_iModjo:int):void
		{
			m_iModjo -= _iModjo;
			if (m_iModjo < 0)
				m_iModjo = 0;
		}
		
		/**
		 * Respawn du personnage.
		 */
		public function Respawn(_Position:Point):void
		{
			x = _Position.x;
			y = _Position.y;
		}
		
		/**
		 * Renvoi true si le joueur est mort.
		 * @return true si le joueur est mort.
		 */
		public function IsDead():Boolean
		{
			return m_iLife <= 0;
		}
		
		/**
		 * On change de controlleur temporairement.
		 * @param	_Controler Le nouveau controlleur.
		 */
		public function SetExternalControler(_Controler:IControler):void
		{
			if (_Controler)
			{
				m_ExtControler = m_Controler;
				m_Controler = _Controler;
			}
		}
		
		/**
		 * On remet le controlleur par défaut.
		 */
		public function ReleaseExternalControler():void
		{
			if (m_ExtControler)
			{
				m_Controler = m_ExtControler;
				m_ExtControler = null;
			}
		}
		
		/**
		 * Renvoi la vitesse du personnage, ou du controlleur externe.
		 * @return La vitesse du personnage.
		 */
		public function GetCurrentSpeed():int
		{
			if (m_ExtControler)
				return m_Controler.GetSpeed();
			else
				return m_iSpeed;
		}

		protected var m_iLife:int;  				///< Point de vie.
		protected var m_iModjo:int; 				///< Point de pouvoir (que l'on gagne si c'est un ennemie).
		protected var m_iSpeed:int;					///< Vitesse de déplacement.
		
		private var m_Controler:IControler;			///< Le "cerveau" de l'entité.
		private var m_ExtControler:IControler;		///< On memorise le controlleur de base lorsqu'il y a un nouveau controlleur.
		private var m_bJump:Boolean; 				///< True si le personnage saute.
		private var m_iVelocity:int;				///< Vitesse de déplacement courante du personnage.
		private var m_fJumpForce:Number;			///< Force du saut.
		private var m_bFall:Boolean;				///< True si le personnage tombe.
		private var m_aAnimations:Object = { };  	///< Liste des animations.
		private var m_bCanMove:Boolean;
		private var m_fJumpDuration:Number;			///< Durée du saut.
		private var m_safeDuration:Number;			///< Temps passé sans collisionner avec un obstacle
		private var m_accRate:int;					///< Temps passé sans collisions requis pour avancer
		private var m_bGoSwim:Boolean;
		private var m_bSwim:Boolean;
		private var m_bUp:Boolean;
		private var m_bDown:Boolean;
		private var m_Stamina:CStamina;
		private var m_nStamina:Number;
		private var m_PlongeSfx:Sfx;
		private var m_nId:int;
		private var m_bCrouch:Boolean;
	}
}